#include <rtthread.h>
#include <rtdevice.h>
#include <board.h>

#include <stdlib.h>
#include <string.h>

#include "stm32h7xx_hal.h"

#define DBG_TAG "sdram"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>

#define SDRAM_Size                               (32 * 1024 * 1024)              //32M字节
#define SDRAM_BANK_ADDR                          ((uint32_t)0xC0000000)          //FMC SDRAM 数据基地址
#define FMC_COMMAND_TARGET_BANK                  FMC_SDRAM_CMD_TARGET_BANK1      //SDRAM 的bank选择
#define SDRAM_TIMEOUT                            ((uint32_t)0x1000)              //超时判断时间

#define SDRAM_MODEREG_BURST_LENGTH_1             ((uint16_t)0x0000)
#define SDRAM_MODEREG_BURST_LENGTH_2             ((uint16_t)0x0001)
#define SDRAM_MODEREG_BURST_LENGTH_4             ((uint16_t)0x0002)
#define SDRAM_MODEREG_BURST_LENGTH_8             ((uint16_t)0x0004)
#define SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL      ((uint16_t)0x0000)
#define SDRAM_MODEREG_BURST_TYPE_INTERLEAVED     ((uint16_t)0x0008)
#define SDRAM_MODEREG_CAS_LATENCY_2              ((uint16_t)0x0020)
#define SDRAM_MODEREG_CAS_LATENCY_3              ((uint16_t)0x0030)
#define SDRAM_MODEREG_OPERATING_MODE_STANDARD    ((uint16_t)0x0000)
#define SDRAM_MODEREG_WRITEBURST_MODE_PROGRAMMED ((uint16_t)0x0000)
#define SDRAM_MODEREG_WRITEBURST_MODE_SINGLE     ((uint16_t)0x0200)


static SDRAM_HandleTypeDef hsdram1;            // SDRAM_HandleTypeDef 结构体变量
static FMC_SDRAM_CommandTypeDef command;       // 控制指令
struct rt_memheap sdram_heap;


void SDRAM_Initialization_Sequence(SDRAM_HandleTypeDef *hsdram, FMC_SDRAM_CommandTypeDef *Command)
{
    __IO uint32_t tmpmrd = 0;

    /* Configure a clock configuration enable command */
    Command->CommandMode                = FMC_SDRAM_CMD_CLK_ENABLE; // 开启SDRAM时钟
    Command->CommandTarget              = FMC_COMMAND_TARGET_BANK;  // 选择要控制的区域
    Command->AutoRefreshNumber          = 1;
    Command->ModeRegisterDefinition     = 0;

    HAL_SDRAM_SendCommand(hsdram, Command, SDRAM_TIMEOUT);  // 发送控制指令
    HAL_Delay(1);       // 延时等待

    /* Configure a PALL (precharge all) command */
    Command->CommandMode                = FMC_SDRAM_CMD_PALL;       // 预充电命令
    Command->CommandTarget              = FMC_COMMAND_TARGET_BANK;  // 选择要控制的区域
    Command->AutoRefreshNumber          = 1;
    Command->ModeRegisterDefinition     = 0;

    HAL_SDRAM_SendCommand(hsdram, Command, SDRAM_TIMEOUT);  // 发送控制指令

    /* Configure a Auto-Refresh command */
    Command->CommandMode                = FMC_SDRAM_CMD_AUTOREFRESH_MODE;   // 使用自动刷新
    Command->CommandTarget              = FMC_COMMAND_TARGET_BANK;          // 选择要控制的区域
    Command->AutoRefreshNumber          = 8;                                // 自动刷新次数
    Command->ModeRegisterDefinition     = 0;

    HAL_SDRAM_SendCommand(hsdram, Command, SDRAM_TIMEOUT);  // 发送控制指令

    /* Program the external memory mode register */
    tmpmrd = (uint32_t)SDRAM_MODEREG_BURST_LENGTH_2         |
                            SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL   |
                            SDRAM_MODEREG_CAS_LATENCY_3           |
                            SDRAM_MODEREG_OPERATING_MODE_STANDARD |
                            SDRAM_MODEREG_WRITEBURST_MODE_SINGLE;

    Command->CommandMode                = FMC_SDRAM_CMD_LOAD_MODE;  // 加载模式寄存器命令
    Command->CommandTarget              = FMC_COMMAND_TARGET_BANK;  // 选择要控制的区域
    Command->AutoRefreshNumber          = 1;
    Command->ModeRegisterDefinition     = tmpmrd;

    HAL_SDRAM_SendCommand(hsdram, Command, SDRAM_TIMEOUT);  // 发送控制指令

    HAL_SDRAM_ProgramRefreshRate(hsdram, 918);  // 配置刷新率
}


int MX_FMC_Init(void)
{
    FMC_SDRAM_TimingTypeDef SdramTiming = {0};

    hsdram1.Instance = FMC_SDRAM_DEVICE;
    /* hsdram1.Init */
    hsdram1.Init.SDBank                 = FMC_SDRAM_BANK1;                          // 选择BANK区
    hsdram1.Init.ColumnBitsNumber       = FMC_SDRAM_COLUMN_BITS_NUM_9;         // 行地址宽度
    hsdram1.Init.RowBitsNumber          = FMC_SDRAM_ROW_BITS_NUM_13;           // 列地址线宽度
    hsdram1.Init.MemoryDataWidth        = FMC_SDRAM_MEM_BUS_WIDTH_16;          // 数据宽度
    hsdram1.Init.InternalBankNumber     = FMC_SDRAM_INTERN_BANKS_NUM_4;        // bank数量
    hsdram1.Init.CASLatency             = FMC_SDRAM_CAS_LATENCY_3;             //   CAS
    hsdram1.Init.WriteProtection        = FMC_SDRAM_WRITE_PROTECTION_DISABLE;  // 禁止写保护
    hsdram1.Init.SDClockPeriod          = FMC_SDRAM_CLOCK_PERIOD_2;            // 分频
    hsdram1.Init.ReadBurst              = FMC_SDRAM_RBURST_ENABLE;             // 突发模式
    hsdram1.Init.ReadPipeDelay          = FMC_SDRAM_RPIPE_DELAY_1;             // 读延迟

    /* SdramTiming */
    SdramTiming.LoadToActiveDelay       = 2;
    SdramTiming.ExitSelfRefreshDelay    = 7;
    SdramTiming.SelfRefreshTime         = 4;
    SdramTiming.RowCycleDelay           = 7;
    SdramTiming.WriteRecoveryTime       = 2;
    SdramTiming.RPDelay                 = 2;
    SdramTiming.RCDDelay                = 2;

    HAL_SDRAM_Init(&hsdram1, &SdramTiming); // 初始化FMC接口

    SDRAM_Initialization_Sequence(&hsdram1,&command);//配置SDRAM

    return 0;
}


int rt_sdram_register(void)
{
    //注册内存堆到内存堆管理
    rt_memheap_init(&sdram_heap, SDRAM_NAME, (void*)SDRAM_START, SDRAM_SIZE);
    return 0;
}







void sdram(int argc, char *argv[])
{
    uint32_t    size;
    rt_tick_t   start,end;
    float       speed;

    uint16_t *iptr = NULL;
    uint16_t *irptr = NULL;
    uint16_t *eptr = NULL;

    if (argc != 2) {
        LOG_E("error! eg:sram <size>");
        return;
    }
    size = atoi(argv[1]);
    if (size >= SDRAM_SIZE - 256) {
        LOG_E("error! out of sram size:SRAM_SIZE - 256");
        return;
    }

//    wptr = rt_memheap_alloc(&sdram_heap, size);
    iptr = rt_malloc(size);
    if (iptr == NULL) {
        LOG_E("no enough memery in %s heap!", SDRAM_NAME);
        return;
    }
    irptr = rt_malloc(size);
    if (irptr == NULL) {
        LOG_E("no enough memery in %s heap!", SDRAM_NAME);
        return;
    }
    eptr = rt_memheap_alloc(&sdram_heap, size);
    if (eptr == NULL) {
        LOG_E("no enough memery in %s heap!", SDRAM_NAME);
        return;
    }

    /* 写内部SRAM,生成随机数 */
    start = rt_tick_get();
    for (uint32_t i = 0; i < size / 2; i++) {
        iptr[i] = (uint16_t)rand();
    }
    end = rt_tick_get();
    speed = size / ((end - start) * 1.0);
    speed *= 1000;
    speed = speed / 1024;
    rt_kprintf("Isram write ok,addr:%08X,size:%d,time:%dms,speed:%.2fKB/s\r\n", iptr, size, end - start, speed);

    /* 从内部SRAM搬运到外部SRAM */
    start = rt_tick_get();
//    for (uint32_t i = 0; i < size / 2; i++) {
//        eptr[i] = iptr[i];
//    }
    memcpy((void*)eptr, (void*)iptr, size);
    end = rt_tick_get();
    speed = size / ((end - start) * 1.0);
    speed *= 1000;
    speed = speed / 1024;
    rt_kprintf("SDram write ok,addr:%08X,size:%d,time:%dms,speed:%.2fKB/s\r\n", eptr, size, end - start, speed);

    /* 从外部内存读取到内部内存 */
    start = rt_tick_get();
//    for (uint32_t i = 0; i < size / 2; i++) {
//        irptr[i] = eptr[i];
//    }
    memcpy((void*)irptr, (void*)eptr, size);
    end = rt_tick_get();
    speed = size / ((end - start) * 1.0);
    speed *= 1000;
    speed = speed / 1024;
    rt_kprintf("SDram read ok,addr:%08X,size:%d,time:%dms,speed:%.2fKB/s\r\n", irptr, size, end - start, speed);

    /* 校验数据 */
    for (uint32_t i = 0; i < size / 2; i++) {
        if ( iptr[i] != irptr[i]) {
            LOG_E("Verify failed! iptr[%d] = %d, irptr[%d] = %d", i, iptr[i], i, irptr[i]);
            return;
        }
    }
    LOG_I("Verify success.");

    if (iptr != NULL) {
        free((void*)iptr);
    }
    if (eptr != NULL) {
        free((void*)eptr);
    }
    if (irptr != NULL) {
        free((void*)irptr);
    }
}


uint8_t sdram_test(void)
{
    uint32_t i = 0;         // 计数变量
    uint16_t ReadData = 0;  // 读取到的数据
    uint8_t  ReadData_8b;

    uint32_t ExecutionTime_Begin;       // 开始时间
    uint32_t ExecutionTime_End;     // 结束时间
    uint32_t ExecutionTime;             // 执行时间
    float    ExecutionSpeed;            // 执行速度

    rt_kprintf("\r\n*******************************************************************************************\r\n");
    rt_kprintf("\r\n进行速度测试>>>\r\n");

// 写入 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

    ExecutionTime_Begin     = HAL_GetTick();    // 获取 systick 当前时间，单位ms

    for (i = 0; i < SDRAM_Size/2; i++)
    {
        *(__IO uint16_t*) (SDRAM_BANK_ADDR + 2*i) = (uint16_t)i;        // 写入数据
    }
    ExecutionTime_End       = HAL_GetTick();                                            // 获取 systick 当前时间，单位ms
    ExecutionTime  = ExecutionTime_End - ExecutionTime_Begin;               // 计算擦除时间，单位ms
    ExecutionSpeed = (float)SDRAM_Size /1024/1024 /ExecutionTime*1000 ;     // 计算速度，单位 MB/S

    rt_kprintf("\r\n以16位数据宽度写入数据，大小：%d MB，耗时: %d ms, 写入速度：%.2f MB/s\r\n",SDRAM_Size/1024/1024,ExecutionTime,ExecutionSpeed);

// 读取   >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

    ExecutionTime_Begin     = HAL_GetTick();    // 获取 systick 当前时间，单位ms

    for(i = 0; i < SDRAM_Size/2;i++ )
    {
        ReadData = *(__IO uint16_t*)(SDRAM_BANK_ADDR + 2 * i );  // 从SDRAM读出数据
    }
    ExecutionTime_End       = HAL_GetTick();                                            // 获取 systick 当前时间，单位ms
    ExecutionTime  = ExecutionTime_End - ExecutionTime_Begin;               // 计算擦除时间，单位ms
    ExecutionSpeed = (float)SDRAM_Size /1024/1024 /ExecutionTime*1000 ;     // 计算速度，单位 MB/S

    rt_kprintf("\r\n读取数据完毕，大小：%d MB，耗时: %d ms, 读取速度：%.2f MB/s\r\n",SDRAM_Size/1024/1024,ExecutionTime,ExecutionSpeed);

// 数据校验 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

    rt_kprintf("\r\n*******************************************************************************************\r\n");
    rt_kprintf("\r\n进行数据校验>>>\r\n");

    for(i = 0; i < SDRAM_Size/2;i++ )
    {
        ReadData = *(__IO uint16_t*)(SDRAM_BANK_ADDR + 2 * i );  // 从SDRAM读出数据
        if( ReadData != (uint16_t)i )      //检测数据，若不相等，跳出函数,返回检测失败结果。
        {
            rt_kprintf("\r\nSDRAM测试失败！！\r\n");
            return ERROR;    // 返回失败标志
        }
    }

    rt_kprintf("\r\n16位数据宽度读写通过，以8位数据宽度写入数据\r\n");
    for (i = 0; i < 255; i++)
    {
        *(__IO uint8_t*) (SDRAM_BANK_ADDR + i) =  (uint8_t)i;
    }
    rt_kprintf("写入完毕，读取数据并比较...\r\n");
    for (i = 0; i < 255; i++)
    {
        ReadData_8b = *(__IO uint8_t*) (SDRAM_BANK_ADDR + i);
        if( ReadData_8b != (uint8_t)i )      //检测数据，若不相等，跳出函数,返回检测失败结果。
        {
            rt_kprintf("8位数据宽度读写测试失败！！\r\n");
            rt_kprintf("请检查NBL0和NBL1的连接\r\n");
            return ERROR;    // 返回失败标志
        }
    }
    rt_kprintf("8位数据宽度读写通过\r\n");
    rt_kprintf("SDRAM读写测试通过，系统正常\r\n");
    return SUCCESS;  // 返回成功标志
}


int rt_sdram_init_then_register(void)
{
    MX_FMC_Init();
    rt_sdram_register();
}
INIT_COMPONENT_EXPORT(rt_sdram_init_then_register);


MSH_CMD_EXPORT(sdram, SDRAM Speed Test.);
MSH_CMD_EXPORT(sdram_test, SDRAM Speed Test.);

